﻿#region Using directives
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using GeoFramework.Gps.IO;
using gpsMe.Classes;
using GeoFramework.Gps;
using GeoFramework.Gps.Nmea;
#endregion

namespace gpsMe.GPS
{
    /// <summary>
    /// GPS manager that manages the communications between GPS devices and gpsMe
    /// </summary>
    public class gpsMeGpsProvider
    {
        #region Instance
        /// <summary>
        /// gpsMeGpsManager current instance
        /// </summary>
        private static gpsMeGpsProvider instance;

        /// <summary>
        /// Indicates if instance is null
        /// </summary>
        private static bool isInstanciated = false;

        /// <summary>
        /// Instance accesor
        /// </summary>
        public static gpsMeGpsProvider Instance
        {
            get
            {
                if (!isInstanciated)
                {
                    new gpsMeGpsProvider();
                    isInstanciated = true;
                }
                return instance;
            }
            private set
            {
                instance = value;
            }
        }
        #endregion

        #region Attributes
        /// <summary>
        /// Current GPS device
        /// </summary>
        private Device device;

        /// <summary>
        /// Current NMEA Interpreter
        /// </summary>
        private NmeaInterpreter interpreter = new NmeaInterpreter();
        #endregion

        #region Accessors
        /// <summary>
        /// Current speed
        /// </summary>
        public double Speed { get; private set; }

        /// <summary>
        /// Current altitude
        /// </summary>
        public double Altitude { get; private set; }

        /// <summary>
        /// Current location
        /// </summary>
        public gpsMeLocationPoint Position { get; private set; }

        /// <summary>
        /// Current number of fixed satellites
        /// </summary>
        public int NumberOfSatellites { get; private set; }

        /// <summary>
        /// Invoked when the current position has changed
        /// </summary>
        public event EventHandler PositionChanged;

        /// <summary>
        /// Invoked when the current speed has changed
        /// </summary>
        public event EventHandler SpeedChanged;

        /// <summary>
        /// Invoked when the current altitude has chnged
        /// </summary>
        public event EventHandler AltitudeChanged;

        /// <summary>
        /// Invoked when the current number of satellites has changed
        /// </summary>
        public event EventHandler NumberOfSatellitesChanged;
        #endregion

        #region Constructor
        /// <summary>
        /// Default Constructor
        /// </summary>
        public gpsMeGpsProvider()
        {
            Instance = this;
            gpsMeGpsLogger.Instance.Text = "Initializing Events...\r\n";
            Devices.IsOnlyFirstDeviceDetected = false; // We want that the best device is used, so we won't stop at the first discovered
            this.InitializeEvents();
            Devices.BeginDetection(); // Detection starts at instantiation to gain time when we'll need to use it
        }
        #endregion

        #region Methods / Functions / Events
        #region Event Handler Initialization
        /// <summary>
        /// Initializes all the used event handlers
        /// </summary>
        private void InitializeEvents()
        {
            Devices.DeviceDetectionStarted += new EventHandler(Devices_DeviceDetectionStarted);
            Devices.DeviceDetectionCompleted += new EventHandler(Devices_DeviceDetectionCompleted);
            Devices.DeviceDetected += new EventHandler<DeviceEventArgs>(Devices_DeviceDetected);
            Devices.DeviceDiscovered += new EventHandler<DeviceEventArgs>(Devices_DeviceDiscovered);
            Devices.DeviceDetectionAttempted += new EventHandler<DeviceEventArgs>(Devices_DeviceDetectionAttempted);
            Devices.DeviceDetectionAttemptFailed += new EventHandler<DeviceDetectionExceptionEventArgs>(Devices_DeviceDetectionAttemptFailed);
            this.interpreter.FixAcquired += new EventHandler(Devices_FixAcquired);
            this.interpreter.FixLost += new EventHandler(Devices_FixLost);
            //this.interpreter.AltitudeChanged += new EventHandler<GeoFramework.DistanceEventArgs>(Devices_AltitudeChanged);
            this.interpreter.PositionChanged += new EventHandler<GeoFramework.PositionEventArgs>(Devices_PositionChanged);
            //this.interpreter.SatellitesChanged += new EventHandler<GeoFramework.Gps.SatelliteListEventArgs>(Devices_SatellitesChanged);
            //this.interpreter.SpeedChanged += new EventHandler<GeoFramework.SpeedEventArgs>(Devices_SpeedChanged);
        }
        #endregion

        #region Devices events
        #region Device speed changed
        /// <summary>
        /// Invoked when the current speed has changed
        /// </summary>
        /// <param name="p_oSender"></param>
        /// <param name="p_oEventArgs"></param>
        private void Devices_SpeedChanged(object p_oSender, GeoFramework.SpeedEventArgs p_oEventArgs)
        {
            GeoFramework.Speed oSpeed = p_oEventArgs.Speed;
            if ((!oSpeed.IsEmpty) && (!oSpeed.IsInfinity) && (!oSpeed.IsInvalid))
            {
                this.Speed = oSpeed.Value;
                gpsMeGpsLogger.Instance.Text = "New speed: " + oSpeed.Value.ToString() + " Km/h\r\n";
                this.SpeedChanged(this, new EventArgs());
            }
        }
        #endregion

        #region Device satellites changed
        /// <summary>
        /// Invoked when the current sattellites has changed
        /// </summary>
        /// <param name="p_oSender"></param>
        /// <param name="p_oEventArgs"></param>
        private void Devices_SatellitesChanged(object p_oSender, GeoFramework.Gps.SatelliteListEventArgs p_oEventArgs)
        {
            int iOldNumberOfSatellites = this.NumberOfSatellites;
            this.NumberOfSatellites = 0;
            foreach (Satellite oSatellite in p_oEventArgs.Satellites)
            {
                if (oSatellite.IsActive && oSatellite.IsFixed)
                    this.NumberOfSatellites++;
            }
            if (iOldNumberOfSatellites != this.NumberOfSatellites)
            {
                gpsMeGpsLogger.Instance.Text = "New number of satellites: " + this.NumberOfSatellites + "\r\n";
                this.NumberOfSatellitesChanged(this, new EventArgs());
            }
        }
        #endregion

        #region Device position changed
        /// <summary>
        /// Invoked when the current position has changed
        /// </summary>
        /// <param name="p_oSender"></param>
        /// <param name="p_oEventArgs"></param>
        private void Devices_PositionChanged(object p_oSender, GeoFramework.PositionEventArgs p_oEventArgs)
        {
            if ((!p_oEventArgs.Position.IsEmpty) && (!p_oEventArgs.Position.IsInvalid))
            {
                this.Position = new gpsMeLocationPoint(p_oEventArgs.Position.Latitude.DecimalDegrees, p_oEventArgs.Position.Longitude.DecimalDegrees);
                gpsMeGpsLogger.Instance.Text = "New position: " + p_oEventArgs.Position.Latitude.DecimalDegrees.ToString() + " ; " + p_oEventArgs.Position.Longitude.DecimalDegrees.ToString() + "\r\n";
                if (this.Altitude != this.interpreter.Altitude.ToMeters().Value)
                {
                    this.Altitude = this.interpreter.Altitude.ToMeters().Value;
                    gpsMeGpsLogger.Instance.Text = "New altitude: " + this.interpreter.Altitude.ToMeters().Value.ToString() + " m\r\n";
                    this.AltitudeChanged(this, new EventArgs());
                }
            }
            this.PositionChanged(this, new EventArgs());
        }
        #endregion

        #region Device altitude changed
        /// <summary>
        /// Invoked when the current altitude has changed
        /// </summary>
        /// <param name="p_oSender"></param>
        /// <param name="p_oEventArgs"></param>
        private void Devices_AltitudeChanged(object p_oSender, GeoFramework.DistanceEventArgs p_oEventArgs)
        {
            if ((!p_oEventArgs.Distance.IsEmpty) && (!p_oEventArgs.Distance.IsInfinity) && (!p_oEventArgs.Distance.IsInvalid))
                this.Altitude = p_oEventArgs.Distance.ToMeters().Value;
            gpsMeGpsLogger.Instance.Text = "New altitude: " + p_oEventArgs.Distance.ToMeters().Value.ToString() + " m\r\n";
            this.AltitudeChanged(this, new EventArgs());
        }
        #endregion

        #region Device fix lost
        /// <summary>
        /// Invoked when the fix has been lost
        /// </summary>
        /// <param name="p_oSender"></param>
        /// <param name="p_oEventArgs"></param>
        private void Devices_FixLost(object p_oSender, EventArgs p_oEventArgs)
        {
            gpsMeGpsLogger.Instance.Text = "Fix has been lost\r\n";
        }
        #endregion

        #region Device fix acquired
        /// <summary>
        /// Invoked when the fix is acquired
        /// </summary>
        /// <param name="p_oSender"></param>
        /// <param name="p_oEventArgs"></param>
        private void Devices_FixAcquired(object p_oSender, EventArgs p_oEventArgs)
        {
            gpsMeGpsLogger.Instance.Text = "Fix is acquired\r\n";
        }
        #endregion

        #region Device detection
        /// <summary>
        /// Invoked when the GPS device dectection attempt has failed
        /// </summary>
        /// <param name="p_oSender"></param>
        /// <param name="p_oEventArgs"></param>
        private void Devices_DeviceDetectionAttemptFailed(object p_oSender, DeviceDetectionExceptionEventArgs p_oEventArgs)
        {
            gpsMeGpsLogger.Instance.Text = "Device " + p_oEventArgs.Device.Name  + " detection attempt failed\r\n";
        }

        /// <summary>
        /// Invoked when a GPS device detection is attempted
        /// </summary>
        /// <param name="p_oSender"></param>
        /// <param name="p_oEventArgs"></param>
        private void Devices_DeviceDetectionAttempted(object p_oSender, DeviceEventArgs p_oEventArgs)
        {
            gpsMeGpsLogger.Instance.Text = "Device " + p_oEventArgs.Device.Name + " detection attempt started\r\n";
        }

        /// <summary>
        /// Invoked when a device has been discovered
        /// </summary>
        /// <param name="p_oSender"></param>
        /// <param name="p_oEventArgs"></param>
        private void Devices_DeviceDiscovered(object p_oSender, DeviceEventArgs p_oEventArgs)
        {
            gpsMeGpsLogger.Instance.Text = "Device " + p_oEventArgs.Device.Name + " has been discovered\r\n";
        }

        /// <summary>
        /// Invoked when a device is detected
        /// </summary>
        /// <param name="p_oSender"></param>
        /// <param name="p_oEventArgs"></param>
        private void Devices_DeviceDetected(object p_oSender, DeviceEventArgs p_oEventArgs)
        {
            gpsMeGpsLogger.Instance.Text = "Device " + p_oEventArgs.Device.Name + " has been detected\r\n";
            if ((this.device != null) && (!this.device.Reliability.IsEmpty))
            {
                if ((this.device.Reliability.Value < p_oEventArgs.Device.Reliability.Value) && p_oEventArgs.Device.IsGpsDevice && (!p_oEventArgs.Device.Reliability.IsEmpty))
                {
                    this.device = p_oEventArgs.Device;
                    gpsMeGpsLogger.Instance.Text = "Device " + p_oEventArgs.Device.Name + " has been choosen\r\n";
                }
            }
            else
            {
                gpsMeGpsLogger.Instance.Text = "Device " + p_oEventArgs.Device.Name + " has been choosen\r\n";
                this.device = p_oEventArgs.Device;
            }
        }

        /// <summary>
        /// Invoked when the device detection is completed
        /// </summary>
        /// <param name="p_oSender"></param>
        /// <param name="p_oEventArgs"></param>
        private void Devices_DeviceDetectionCompleted(object p_oSender, EventArgs p_oEventArgs)
        {
            gpsMeGpsLogger.Instance.Text = "Device detection has been completed\r\n";
            if (this.device != null)
            {
                gpsMeGpsLogger.Instance.Text = "The selected device is " + this.device.Name + "\r\n";
                gpsMeGpsLogger.Instance.Text = "\tReliability: " + this.device.Reliability.Value.ToString() + "\r\n";
                gpsMeGpsLogger.Instance.Text = "\tIsGpsDevice: " + this.device.IsGpsDevice.ToString() + "\r\n";
                gpsMeGpsLogger.Instance.Text = "\tIsOpen: " + this.device.IsOpen.ToString() + "\r\n";
                if (!this.device.IsOpen)
                {
                    gpsMeGpsLogger.Instance.Text = "Opening the device...\r\n";
                    this.device.Open();
                }
                gpsMeGpsLogger.Instance.Text = "\tIsOpen: " + this.device.IsOpen.ToString() + "\r\n";
                this.interpreter.Start(this.device);
            }
            else
            {
                gpsMeGpsLogger.Instance.Text = "No device found\r\n";
            }
        }

        /// <summary>
        /// Invoked when the device detection has started
        /// </summary>
        /// <param name="p_oSender"></param>
        /// <param name="p_oEventArgs"></param>
        private void Devices_DeviceDetectionStarted(object p_oSender, EventArgs p_oEventArgs)
        {
            gpsMeGpsLogger.Instance.Text = "The device detection has started\r\n";
        }
        #endregion
        #endregion
        #endregion
    }
}
